<?php
/**
 * Created by PhpStorm.
 * User: meng
 * Date: 2021/7/26
 * Time: 1:32 下午
 */

namespace app\admin\model;


use app\lib\exception\FileException;
use Carbon\Carbon;
use Exception;
use Ramsey\Uuid\Uuid;
use think\facade\Config;
use think\facade\Filesystem;
use think\facade\Request;
use think\model\concern\SoftDelete;

class FileModel extends AdminBaseModel
{
    use SoftDelete;

    protected $table = 'file';
    protected $deleteTime = 'delete_time';
    protected $hidden = ['delete_time'];

    private static $storeDir = null;
    private static $fileDisk = null;

    // 上传分片
    public static function uploadChunk ($files) {
        $chunk = $md5 = $chunkname = null;
        extract(self::verifyChunk($files));
        // 保存切片信息
        self::writeChunk($chunk, $md5, $chunkname);
    }

    // 合并分片
    public static function mergeChunks ($params): array {
        $chunkPaths = self::verifyMergeChunks($params['md5'], $params['file_chunk_num']);
        $ret = self::chunksCombine($chunkPaths, $params['filename'], $params['md5']);
        self::create([
            'path'        => $ret['path'],
            'name'        => $ret['name'],
            'ext'         => $ret['ext'],
            'size'        => $ret['size'],
            'md5'         => $params['md5'],
            'create_time' => (string)Carbon::now()
        ]);
        return [
            'path' => $ret['path'],
            'url'  => '/' . $ret['path']
        ];
    }

    // 校验文件是否存在
    public static function checkPresence ($params): array {
        $response = [
            'presence' => false,
            'path'     => '',
            'url'      => ''
        ];
        $exists = self::where('md5', $params['md5'])->find();
        if (!empty($exists)) {
            $response['presence'] = true;
            $response['path'] = $exists['path'];
            $response['url'] = '/' . $exists['path'];
        }
        return $response;
    }

    // 将分片合并到一个文件,删除分片目录及分片
    private static function chunksCombine ($chunkPaths, $originName, $md5): array {
        $ext = pathinfo($originName, PATHINFO_EXTENSION);
        $md5Path = join_paths(self::getDisk(), self::getStoreDir(), 'tmp', $md5);
        $local = date('Ymd');
        $destDir = join_paths(self::getDisk(), self::getStoreDir(), $local);
        if (!file_exists($destDir)) {
            mkdir($destDir, 0777, true);
        }
        $newFileName = ((string)Uuid::uuid4()) . (empty($ext) ? '' : '.' . $ext);
        $newFile = join_paths($destDir, $newFileName);
        $size = 0;
        $fp = fopen($newFile, 'w');
        for ($i = 0; $i < count($chunkPaths); $i++) {
            $tempFilePath = join_paths($md5Path, $chunkPaths[$i]);
            $tmpSize = filesize($tempFilePath);
            $size += $tmpSize;
            $tf = fopen($tempFilePath, 'r');
            fwrite($fp, fread($tf, $tmpSize));
            fclose($tf);
        }
        fclose($fp);
        // 删除临时文件和目录
        self::rmTempFile($md5Path);
        return [
            'ext'  => $ext,
            'path' => join_paths(self::getStoreDir(), $local, $newFileName),
            'size' => $size,
            'name' => $originName
        ];
    }

    // 递归删除目录和文件
    final private static function rmTempFile ($path): bool {
        if ($path == '/' || $path == '..' || $path == '.') {
            return false;
        }
        $dirs = scandir($path);
        foreach ($dirs as $dir) {
            if ($dir != '.' && $dir != '..') {
                $sdir = join_paths($path, $dir);
                if (is_dir($sdir)) {
                    self::rmTempFile($sdir);
                } else {
                    unlink($sdir);
                }
            }
        }
        rmdir($path);
        return true;
    }

    // 校验分片
    private static function verifyMergeChunks ($md5, $chunkNum): array {
        $md5Path = join_paths(self::getDisk(), self::getStoreDir(), 'tmp', $md5);
        if (!file_exists($md5Path)) {
            throw new FileException('切片目录不存在');
        }
        $chunkPaths = scandir($md5Path);
        $chunkPaths = array_values(array_filter($chunkPaths, function ($item) {
            return $item != '.' && $item != '..';
        }));
        if (empty($chunkPaths) || count($chunkPaths) != $chunkNum) {
            throw new FileException('切片数量不正确');
        }
        return $chunkPaths;
    }

    // 获取硬盘配置
    private static function getDisk () {
        if (!is_null(self::$fileDisk)) {
            return self::$fileDisk;
        }
        $disk = Config::get('filesystem.disks.file.root');
        if (is_null($disk)) {
            throw new FileException('请配置文件存放硬盘目录');
        }
        self::$fileDisk = $disk;
        return self::$fileDisk;
    }

    // 保存切片(使用框架Filesystem)
    private static function writeChunk ($chunk, $md5, $chunkname) {
        $storeDir = self::getStoreDir();
        try {
            Filesystem::disk('file')->putFileAs($storeDir, $chunk, 'tmp' . '/' . $md5 . '/' . $md5 . '-' . $chunkname);
        } catch (Exception $e) {
            throw new FileException('切片上传失败');
        }
    }

    // 获取存储目录
    private static function getStoreDir () {
        if (is_null(self::$storeDir)) {
            self::$storeDir = Config::get('file.store_dir', '');
        }
        return self::$storeDir;
    }

    // 验证切片信息
    private static function verifyChunk ($files): array {
        if (empty($files)) {
            throw new FileException('请选择文件');
        }
        $md5 = Request::header('md5');
        $filename = Request::header('filename');
        if ($md5 == null || $filename == null) {
            throw new FileException('文件切片信息不完整');
        }
        return [
            'chunk'     => $files[0],
            'md5'       => $md5,
            'chunkname' => $filename
        ];
    }
}
